<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="Seata,分布式事务,XA,AT" />
	<meta name="description" content="深入解读 Seata 的 XA 模式" />
	<!-- 网页标签标题 -->
	<title>分布式事务如何实现？深入解读 Seata 的 XA 模式</title>
  <link rel="shortcut icon" href="/img/seata_logo_small.jpeg"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="//img.alicdn.com/tfs/TB1gqL1w4D1gK0jSZFyXXciOVXa-1497-401.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="https://img.alicdn.com/tfs/TB14eEmw7P2gK0jSZPxXXacQpXa-38-32.png"/><ul><li class="menu-item menu-item-normal"><a href="/zh-cn/index.html" target="_self">首页</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">文档</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/developers/developers_dev.html" target="_self">开发者</a></li><li class="menu-item menu-item-normal menu-item-normal-active"><a href="/zh-cn/blog/index.html" target="_self">博客</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/community/index.html" target="_self">社区</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/blog/download.html" target="_self">下载</a></li></ul></div></div></header><section class="blog-content markdown-body"><h1>分布式事务如何实现？深入解读 Seata 的 XA 模式</h1>
<p>作者简介：煊檍，GitHub ID：sharajava，阿里巴巴中件间 GTS 研发团队负责人，SEATA 开源项目发起人，曾在 Oracle 北京研发中心多年，从事 WebLogic 核心研发工作。长期专注于中间件，尤其是分布式事务领域的技术实践。</p>
<p>Seata 1.2.0 版本重磅发布新的事务模式：XA 模式，实现对 XA 协议的支持。</p>
<p>这里，我们从三个方面来深入解读这个新的特性：</p>
<ul>
<li>是什么（What）：XA 模式是什么？</li>
<li>为什么（Why）：为什么支持 XA？</li>
<li>怎么做（How）：XA 模式是如何实现的，以及怎样使用？</li>
</ul>
<h1>1. XA 模式是什么？</h1>
<p>这里有两个基本的前置概念：</p>
<ol>
<li>什么是 XA？</li>
<li>什么是 Seata 定义的所谓 事务模式？</li>
</ol>
<p>基于这两点，再来理解 XA 模式就很自然了。</p>
<h2>1.1 什么是 XA？</h2>
<p>XA 规范 是 X/Open 组织定义的分布式事务处理（DTP，Distributed Transaction Processing）标准。</p>
<p>XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源（如数据库，应用服务器，消息队列等）在同一事务中访问，这样可以使 ACID 属性跨越应用程序而保持有效。</p>
<p>XA 规范 使用两阶段提交（2PC，Two-Phase Commit）来保证所有资源同时提交或回滚任何特定的事务。</p>
<p>XA 规范 在上世纪 90 年代初就被提出。目前，几乎所有主流的数据库都对 XA 规范 提供了支持。</p>
<h2>1.2 什么是 Seata 的事务模式？</h2>
<p>Seata 定义了全局事务的框架。</p>
<p>全局事务 定义为若干 分支事务 的整体协调：</p>
<ol>
<li>TM 向 TC 请求发起（Begin）、提交（Commit）、回滚（Rollback）全局事务。</li>
<li>TM 把代表全局事务的 XID 绑定到分支事务上。</li>
<li>RM 向 TC 注册，把分支事务关联到 XID 代表的全局事务中。</li>
<li>RM 把分支事务的执行结果上报给 TC。（可选）</li>
<li>TC 发送分支提交（Branch Commit）或分支回滚（Branch Rollback）命令给 RM。</li>
</ol>
<img src="https://img.alicdn.com/tfs/TB19qmhOrY1gK0jSZTEXXXDQVXa-1330-924.png" alt="seata-mod" style="zoom:50%;" />
<p>Seata 的 全局事务 处理过程，分为两个阶段：</p>
<ul>
<li>执行阶段 ：执行 分支事务，并 保证 执行结果满足是 <em>可回滚的（Rollbackable）</em> 和 <em>持久化的（Durable）</em>。</li>
<li>完成阶段： 根据 执行阶段 结果形成的决议，应用通过 TM 发出的全局提交或回滚的请求给 TC，TC 命令 RM 驱动 分支事务 进行 Commit 或 Rollback。</li>
</ul>
<p>Seata 的所谓 事务模式 是指：运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲，应该叫作 分支事务模式。</p>
<p>不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。即，回答以下两个问题：</p>
<ul>
<li>执行阶段 ：如何执行并 保证 执行结果满足是 <em>可回滚的（Rollbackable）</em> 和 <em>持久化的（Durable）</em>。</li>
<li>完成阶段： 收到 TC 的命令后，如何做到分支的提交或回滚？</li>
</ul>
<p>以我们 Seata 的 AT 模式和 TCC 模式为例来理解：</p>
<p>AT 模式</p>
<img src="https://img.alicdn.com/tfs/TB1NTuzOBr0gK0jSZFnXXbRRXXa-1330-924.png" alt="at-mod" style="zoom:50%;" />
<ul>
<li>
<p>执行阶段：</p>
</li>
<li>
<ul>
<li>可回滚：根据 SQL 解析结果，记录回滚日志</li>
<li>持久化：回滚日志和业务 SQL 在同一个本地事务中提交到数据库</li>
</ul>
</li>
<li>
<p>完成阶段：</p>
</li>
<li>
<ul>
<li>分支提交：异步删除回滚日志记录</li>
<li>分支回滚：依据回滚日志进行反向补偿更新</li>
</ul>
</li>
</ul>
<p>TCC 模式</p>
<img src="https://img.alicdn.com/tfs/TB1m59pOEY1gK0jSZFCXXcwqXXa-1330-924.png" alt="tcc-mod" style="zoom:50%;" />
<ul>
<li>
<p>执行阶段：</p>
</li>
<li>
<ul>
<li>调用业务定义的 Try 方法（完全由业务层面保证 <em>可回滚</em> 和 <em>持久化</em>）</li>
</ul>
</li>
<li>
<p>完成阶段：</p>
</li>
<li>
<ul>
<li>分支提交：调用各事务分支定义的 Confirm 方法</li>
<li>分支回滚：调用各事务分支定义的 Cancel 方法</li>
</ul>
</li>
</ul>
<h2>1.3 什么是 Seata 的 XA 模式？</h2>
<p>XA 模式：</p>
<p>在 Seata 定义的分布式事务框架内，利用事务资源（数据库、消息服务等）对 XA 协议的支持，以 XA 协议的机制来管理分支事务的一种 事务模式。</p>
<img src="https://img.alicdn.com/tfs/TB1hSpccIVl614jSZKPXXaGjpXa-1330-924.png" alt="xa-mod" style="zoom:50%;" />
<ul>
<li>
<p>执行阶段：</p>
</li>
<li>
<ul>
<li>可回滚：业务 SQL 操作放在 XA 分支中进行，由资源对 XA 协议的支持来保证 <em>可回滚</em></li>
<li>持久化：XA 分支完成后，执行 XA prepare，同样，由资源对 XA 协议的支持来保证 <em>持久化</em>（即，之后任何意外都不会造成无法回滚的情况）</li>
</ul>
</li>
<li>
<p>完成阶段：</p>
</li>
<li>
<ul>
<li>分支提交：执行 XA 分支的 commit</li>
<li>分支回滚：执行 XA 分支的 rollback</li>
</ul>
</li>
</ul>
<h1>2. 为什么支持 XA？</h1>
<p>为什么要在 Seata 中增加 XA 模式呢？支持 XA 的意义在哪里呢？</p>
<h2>2.1 补偿型事务模式的问题</h2>
<p>本质上，Seata 已经支持的 3 大事务模式：AT、TCC、Saga 都是 补偿型 的。</p>
<p>补偿型 事务处理机制构建在 事务资源 之上（要么在中间件层面，要么在应用层面），事务资源 本身对分布式事务是无感知的。</p>
<img src="https://img.alicdn.com/tfs/TB1z.qyOET1gK0jSZFrXXcNCXXa-602-460.png" alt="img" style="zoom:50%;" />
<p>事务资源 对分布式事务的无感知存在一个根本性的问题：无法做到真正的 全局一致性 。</p>
<p>比如，一条库存记录，处在 补偿型 事务处理过程中，由 100 扣减为 50。此时，仓库管理员连接数据库，查询统计库存，就看到当前的 50。之后，事务因为异外回滚，库存会被补偿回滚为 100。显然，仓库管理员查询统计到的 50 就是 脏 数据。</p>
<p>可以看到，补偿型 分布式事务机制因为不要求 事务资源 本身（如数据库）的机制参与，所以无法保证从事务框架之外的全局视角的数据一致性。</p>
<h2>2.2 XA 的价值</h2>
<p>与 补偿型 不同，XA 协议 要求 事务资源 本身提供对规范和协议的支持。</p>
<img src="https://img.alicdn.com/tfs/TB1vs9kOvb2gK0jSZK9XXaEgFXa-602-486.png" alt="nct" style="zoom:50%;" />
<p>因为 事务资源 感知并参与分布式事务处理过程，所以 事务资源（如数据库）可以保障从任意视角对数据的访问有效隔离，满足全局数据一致性。</p>
<p>比如，上一节提到的库存更新场景，XA 事务处理过程中，中间态数据库存 50 由数据库本身保证，是不会仓库管理员的查询统计 <em>看</em> 到的。（当然隔离级别需要 读已提交 以上）</p>
<p>除了 全局一致性 这个根本性的价值外，支持 XA 还有如下几个方面的好处：</p>
<ol>
<li>业务无侵入：和 AT 一样，XA 模式将是业务无侵入的，不给应用设计和开发带来额外负担。</li>
<li>数据库的支持广泛：XA 协议被主流关系型数据库广泛支持，不需要额外的适配即可使用。</li>
<li>多语言支持容易：因为不涉及 SQL 解析，XA 模式对 Seata 的 RM 的要求比较少，为不同语言开发 SDK 较之 AT 模式将更 <em>薄</em>，更容易。</li>
<li>传统基于 XA 应用的迁移：传统的，基于 XA 协议的应用，迁移到 Seata 平台，使用 XA 模式将更平滑。</li>
</ol>
<h2>2.3 XA 广泛被质疑的问题</h2>
<p>不存在某一种分布式事务机制可以完美适应所有场景，满足所有需求。</p>
<p>XA 规范早在上世纪 90 年代初就被提出，用以解决分布式事务处理这个领域的问题。</p>
<p>现在，无论 AT 模式、TCC 模式还是 Saga 模式，这些模式的提出，本质上都源自 XA 规范对某些场景需求的无法满足。</p>
<p>XA 规范定义的分布式事务处理机制存在一些被广泛质疑的问题，针对这些问题，我们是如何思考的呢？</p>
<ol>
<li><strong>数据锁定</strong>：数据在整个事务处理过程结束前，都被锁定，读写都按隔离级别的定义约束起来。</li>
</ol>
<blockquote>
<p>思考：</p>
<p>数据锁定是获得更高隔离性和全局一致性所要付出的代价。</p>
<p>补偿型 的事务处理机制，在 执行阶段 即完成分支（本地）事务的提交，（资源层面）不锁定数据。而这是以牺牲 隔离性 为代价的。</p>
<p>另外，AT 模式使用 <em>全局锁</em> 保障基本的 <em>写隔离</em>，实际上也是锁定数据的，只不过锁在 TC 侧集中管理，解锁效率高且没有阻塞的问题。</p>
</blockquote>
<ol start="2">
<li><strong>协议阻塞</strong>：XA prepare 后，分支事务进入阻塞阶段，收到 XA commit 或 XA rollback 前必须阻塞等待。</li>
</ol>
<blockquote>
<p>思考：</p>
<p>协议的阻塞机制本身并不是问题，关键问题在于 协议阻塞 遇上 数据锁定。</p>
<p>如果一个参与全局事务的资源 “失联” 了（收不到分支事务结束的命令），那么它锁定的数据，将一直被锁定。进而，甚至可能因此产生死锁。</p>
<p>这是 XA 协议的核心痛点，也是 Seata 引入 XA 模式要重点解决的问题。</p>
<p>基本思路是两个方面：避免 “失联” 和 增加 “自解锁” 机制。（这里涉及非常多技术细节，暂时不展开，在后续 XA 模式演进过程中，会专门拿出来讨论）</p>
</blockquote>
<ol start="3">
<li><strong>性能差</strong>：性能的损耗主要来自两个方面：一方面，事务协调过程，增加单个事务的 RT；另一方面，并发事务数据的锁冲突，降低吞吐。</li>
</ol>
<blockquote>
<p>思考：</p>
<p>和不使用分布式事务支持的运行场景比较，性能肯定是下降的，这点毫无疑问。</p>
<p>本质上，事务（无论是本地事务还是分布式事务）机制就是拿部分 性能的牺牲 ，换来 编程模型的简单 。</p>
<p>与同为 业务无侵入 的 AT 模式比较：</p>
<p>首先，因为同样运行在 Seata 定义的分布式事务框架下，XA 模式并没有产生更多事务协调的通信开销。</p>
<p>其次，并发事务间，如果数据存在热点，产生锁冲突，这种情况，在 AT 模式（默认使用全局锁）下同样存在的。</p>
<p>所以，在影响性能的两个主要方面，XA 模式并不比 AT 模式有非常明显的劣势。</p>
<p>AT 模式性能优势主要在于：集中管理全局数据锁，锁的释放不需要 RM 参与，释放锁非常快；另外，全局提交的事务，完成阶段 异步化。</p>
</blockquote>
<h1>3. XA 模式如何实现以及怎样用？</h1>
<h2>3.1 XA 模式的设计</h2>
<h3>3.1.1 设计目标</h3>
<p>XA 模式的基本设计目标，两个主要方面：</p>
<ol>
<li>从 场景 上，满足 全局一致性 的需求。</li>
<li>从 应用上，保持与 AT 模式一致的无侵入。</li>
<li>从 机制 上，适应分布式微服务架构的特点。</li>
</ol>
<p>整体思路：</p>
<ol>
<li>与 AT 模式相同的：以应用程序中 本地事务 的粒度，构建到 XA 模式的 分支事务。</li>
<li>通过数据源代理，在应用程序本地事务范围外，在框架层面包装 XA 协议的交互机制，把 XA 编程模型 透明化。</li>
<li>把 XA 的 2PC 拆开，在分支事务 执行阶段 的末尾就进行 XA prepare，把 XA 协议完美融合到 Seata 的事务框架，减少一轮 RPC 交互。</li>
</ol>
<h3>3.1.2 核心设计</h3>
<h4>1. 整体运行机制</h4>
<p>XA 模式 运行在 Seata 定义的事务框架内：</p>
<img src="https://img.alicdn.com/tfs/TB1uM2OaSslXu8jSZFuXXXg7FXa-1330-958.png" alt="xa-fw" style="zoom:50%;" />
<ul>
<li>
<p>执行阶段（E xecute）：</p>
</li>
<li>
<ul>
<li>XA start/XA end/XA prepare + SQL + 注册分支</li>
</ul>
</li>
<li>
<p>完成阶段（F inish）：</p>
</li>
<li>
<ul>
<li>XA commit/XA rollback</li>
</ul>
</li>
</ul>
<h4>2. 数据源代理</h4>
<p>XA 模式需要 XAConnection。</p>
<p>获取 XAConnection 两种方式：</p>
<ul>
<li>方式一：要求开发者配置 XADataSource</li>
<li>方式二：根据开发者的普通 DataSource 来创建</li>
</ul>
<p>第一种方式，给开发者增加了认知负担，需要为 XA 模式专门去学习和使用 XA 数据源，与 透明化 XA 编程模型的设计目标相违背。</p>
<p>第二种方式，对开发者比较友好，和 AT 模式使用一样，开发者完全不必关心 XA 层面的任何问题，保持本地编程模型即可。</p>
<p>我们优先设计实现第二种方式：数据源代理根据普通数据源中获取的普通 JDBC 连接创建出相应的 XAConnection。</p>
<p>类比 AT 模式的数据源代理机制，如下：</p>
<img src="/img/xa/pics/ds1.png" alt="img" style="zoom:50%;" />
<p>但是，第二种方法有局限：无法保证兼容的正确性。</p>
<p>实际上，这种方法是在做数据库驱动程序要做的事情。不同的厂商、不同版本的数据库驱动实现机制是厂商私有的，我们只能保证在充分测试过的驱动程序上是正确的，开发者使用的驱动程序版本差异很可能造成机制的失效。</p>
<p>这点在 Oracle 上体现非常明显。参见 Druid issue：<a href="https://github.com/alibaba/druid/issues/3707">https://github.com/alibaba/druid/issues/3707</a></p>
<p>综合考虑，XA 模式的数据源代理设计需要同时支持第一种方式：基于 XA 数据源进行代理。</p>
<p>类比 AT 模式的数据源代理机制，如下：</p>
<img src="/img/xa/pics/ds2.png" alt="img" style="zoom:50%;" />
<h4>3. 分支注册</h4>
<p>XA start 需要 Xid 参数。</p>
<p>这个 Xid 需要和 Seata 全局事务的 XID 和 BranchId 关联起来，以便由 TC 驱动 XA 分支的提交或回滚。</p>
<p>目前 Seata 的 BranchId 是在分支注册过程，由 TC 统一生成的，所以 XA 模式分支注册的时机需要在 XA start 之前。</p>
<p>将来一个可能的优化方向：</p>
<p>把分支注册尽量延后。类似 AT 模式在本地事务提交之前才注册分支，避免分支执行失败情况下，没有意义的分支注册。</p>
<p>这个优化方向需要 BranchId 生成机制的变化来配合。BranchId 不通过分支注册过程生成，而是生成后再带着 BranchId 去注册分支。</p>
<h4>4. 小结</h4>
<p>这里只通过几个重要的核心设计，说明 XA 模式的基本工作机制。</p>
<p>此外，还有包括 <em>连接保持</em>、<em>异常处理</em> 等重要方面，有兴趣可以从项目代码中进一步了解。</p>
<p>以后会陆续写出来和大家交流。</p>
<h3>3.1.3 演进规划</h3>
<p>XA 模式总体的演进规划如下：</p>
<ol>
<li>第 1 步（已经完成）：首个版本（1.2.0），把 XA 模式原型机制跑通。确保只增加，不修改，不给其他模式引入的新问题。</li>
<li>第 2 步（计划 5 月完成）：与 AT 模式必要的融合、重构。</li>
<li>第 3 步（计划 7 月完成）：完善异常处理机制，进行上生产所必需的打磨。</li>
<li>第 4 步（计划 8 月完成）：性能优化。</li>
<li>第 5 步（计划 2020 年内完成）：结合 Seata 项目正在进行的面向云原生的 Transaction Mesh 设计，打造云原生能力。</li>
</ol>
<h2>3.2 XA 模式的使用</h2>
<p>从编程模型上，XA 模式与 AT 模式保持完全一致。</p>
<p>可以参考 Seata 官网的样例：seata-xa</p>
<p>样例场景是 Seata 经典的，涉及库存、订单、账户 3 个微服务的商品订购业务。</p>
<p>在样例中，上层编程模型与 AT 模式完全相同。只需要修改数据源代理，即可实现 XA 模式与 AT 模式之间的切换。</p>
<pre><code>    @Bean(&quot;dataSource&quot;)
    public DataSource dataSource(DruidDataSource druidDataSource) {
        // DataSourceProxy for AT mode
        // return new DataSourceProxy(druidDataSource);

        // DataSourceProxyXA for XA mode
        return new DataSourceProxyXA(druidDataSource);
    }
</code></pre>
<h1>4. 总结</h1>
<p>在当前的技术发展阶段，不存一个分布式事务处理机制可以完美满足所有场景的需求。</p>
<p>一致性、可靠性、易用性、性能等诸多方面的系统设计约束，需要用不同的事务处理机制去满足。</p>
<p>Seata 项目最核心的价值在于：构建一个全面解决分布式事务问题的 标准化 平台。</p>
<p>基于 Seata，上层应用架构可以根据实际场景的需求，灵活选择合适的分布式事务解决方案。</p>
<img src="https://img.alicdn.com/tfs/TB1lTSoOqL7gK0jSZFBXXXZZpXa-1028-528.png" alt="img" style="zoom:50%;" />
<p>XA 模式的加入，补齐了 Seata 在 全局一致性 场景下的缺口，形成 AT、TCC、Saga、XA 四大 事务模式 的版图，基本可以满足所有场景的分布式事务处理诉求。</p>
<p>当然 XA 模式和 Seata 项目本身都还不尽完美，有很多需要改进和完善的地方。非常欢迎大家参与到项目的建设中，共同打造一个标准化的分布式事务平台。</p>
</section><footer class="footer-container"><div class="footer-body"><img src="//img.alicdn.com/tfs/TB1dGrSwVT7gK0jSZFpXXaTkpXa-4802-1285.png"/><p class="docsite-power">website powered by docsite</p><div class="cols-container"><div class="col col-12"><h3>愿景</h3><p>Seata 是一款阿里巴巴开源的分布式事务解决方案，致力于在微服务架构下提供高性能和简单易用的分布式事务服务。</p></div><div class="col col-6"><dl><dt>文档</dt><dd><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">Seata 是什么？</a></dd><dd><a href="/zh-cn/docs/user/quickstart.html" target="_self">快速开始</a></dd><dd><a href="https://github.com/seata/seata.github.io/issues/new" target="_self">报告文档问题</a></dd><dd><a href="https://github.com/seata/seata.github.io" target="_self">在Github上编辑此文档</a></dd></dl></div><div class="col col-6"><dl><dt>资源</dt><dd><a href="/zh-cn/blog/index.html" target="_self">博客</a></dd><dd><a href="/zh-cn/community/index.html" target="_self">社区</a></dd></dl></div></div><div class="copyright"><span>Copyright © 2019 Seata</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
	<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?104e73ef0c18b416b27abb23757ed8ee";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
</body>
</html>
